package com.xz.courseschedule.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xz.courseschedule.algorithm.GeneticAlgorithm;
import com.xz.courseschedule.common.vo.BaseVo;
import com.xz.courseschedule.entity.Room;
import com.xz.courseschedule.entity.Semester;
import com.xz.courseschedule.entity.Task;
import com.xz.courseschedule.entity.Timetable;
import com.xz.courseschedule.mapper.*;
import com.xz.courseschedule.service.TaskService;
import com.xz.courseschedule.utils.ClassUtil;
import com.xz.courseschedule.utils.ConstantInfo;
import com.xz.courseschedule.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author: zys
 * @CreateTime: 2025-05-23
 */

@Service
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService {

    @Autowired
    private SemesterMapper semesterMapper;
    @Autowired
    private TaskMapper taskMapper;
    @Autowired
    private RoomMapper roomMapper;
    @Autowired
    private ClassesMapper classesMapper;
    @Autowired
    private TimetableMapper timetableMapper;
    @Autowired
    private GeneticAlgorithm geneticAlgorithm;

    @Override
    public Result getList() {
        List<Task> list = super.list();
        return Result.success("成功返回任务列表",list);
    }

    @Override
    public Result getById(Long id) {
        if (id == null) {
            return Result.error(400, "任务ID不能为空");
        }
        Task task = super.getById(id);
        if (task == null) {
            return Result.error(404, "任务不存在");
        }
        return Result.success("成功获取任务信息", task);
    }

    @Override
    public Result add(Task task) {
        if (task == null) {
            return Result.error(400, "任务信息不能为空");
        }
        boolean result = super.save(task);
        if (result) {
            return Result.success("任务添加成功", task);
        } else {
            return Result.error(500, "任务添加失败");
        }
    }

    @Override
    public Result update(Task task) {
        if (task == null || task.getId() == null) {
            return Result.error(400, "任务信息或ID不能为空");
        }
        // 检查任务是否存在
        Task existingTask = super.getById(task.getId());
        if (existingTask == null) {
            return Result.error(404, "任务不存在");
        }
        boolean result = super.updateById(task);
        if (result) {
            return Result.success("任务更新成功", task);
        } else {
            return Result.error(500, "任务更新失败");
        }
    }

    @Override
    public Result delete(Long id) {
        if (id == null) {
            return Result.error(400, "任务ID不能为空");
        }
        // 物理删除
        boolean result = super.removeById(id);
        if (result) {
            return Result.success("任务删除成功");
        } else {
            return Result.error(500, "任务删除失败");
        }
    }

    @Override
    public Result deleteBatch(Long[] ids) {
        if (ids == null || ids.length == 0) {
            return Result.error(400, "任务ID列表不能为空");
        }
        // 物理删除
        boolean result = super.removeByIds(Arrays.asList(ids));
        if (result) {
            return Result.success("批量删除任务成功");
        } else {
            return Result.error(500, "批量删除任务失败");
        }
    }

    /**
     * <h1>排课算法<h1/>
     * @param id 学期id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result courseScheduling(Long id) {
        try {
            Semester semester = semesterMapper.selectById(id);//通过学期id查询对于的学期基本信息
            System.out.println("学期【"+semester.getSemesterName()+"】，共"+semester.getSemesterWeeksSum()+"周");
            long start = System.currentTimeMillis();//获取当前系统时间戳
            // 1.获得排课任务表
            List<Task> taskList = taskMapper.selectList(new LambdaQueryWrapper<Task>());
            if (taskList ==null || taskList.isEmpty()){
                return Result.error(400,"自动排课失败，查询不到排课任务!请导入排课任务再进行排课!!!");
            }

            //校验学时是否超过课表的容纳值
            checkWeeksNumber(taskList);

            //2.3. 将开课任务的各项信息进行编码成染色体 -- 同时随机分配时间
            List<String> resultGeneList = coding(taskList);

            //4.将分配好时间的基因编码以班级分类成为以班级的个体，得到班级的不冲突时间初始编码
            Map<String,List<String>> individualMap = geneticAlgorithm.transformIndividual(resultGeneList);

            //5.遗传进化
            individualMap = geneticAlgorithm.geneticEvolution(individualMap);

            //6.分配教室并做教室冲突检测
            List<String> resultList = assignRoom(individualMap);

            //7.解码
            List<Timetable> timetableList = decoding(resultList);

            //8.课程表写入数据库
            timetableMapper.deleteAll(semester.getId());
            for (Timetable timetable:timetableList){
                timetable.setSemesterId(semester.getId());
                timetableMapper.insert(timetable);
            }
            long timeConsume = System.currentTimeMillis() - start;//排课结束时间
            System.out.println("完成自动排课，耗时:"+timeConsume);
            semester.setSemesterStatus(1);
            semesterMapper.updateById(semester);
            return Result.success(String.format("自动排课成功,耗时:%sms", timeConsume));
        }catch (Exception e){
            return Result.error(400,"自动排课失败,出现异常!");
        }
    }

    /**
     * 将编码后的进行提取
     * @param resultList
     * @return
     */
    private List<Timetable> decoding(List<String> resultList){
        List<Timetable> timetableList = new ArrayList<>();
        for (String gene:resultList){
            Timetable timetable = new Timetable();
            timetable.setClassNo(ClassUtil.cutGene(ConstantInfo.CLASS_NO, gene));
            timetable.setCourseNo(ClassUtil.cutGene(ConstantInfo.COURSE_NO, gene));
            timetable.setTeacherNo(ClassUtil.cutGene(ConstantInfo.TEACHER_NO, gene));
            timetable.setRoomNo(ClassUtil.cutGene(ConstantInfo.ROOM_NO, gene));
            timetable.setTimeslot(Integer.parseInt(ClassUtil.cutGene(ConstantInfo.TIMESLOT, gene)));
            timetable.setStartWeek(Integer.parseInt(ClassUtil.cutGene(ConstantInfo.START_WEEK, gene)));
            timetable.setEndWeek(Integer.parseInt(ClassUtil.cutGene(ConstantInfo.END_WEEK, gene)));
            timetable.setBiweekly(Integer.parseInt(ClassUtil.cutGene(ConstantInfo.BIWEEKLY, gene)));
            timetableList.add(timetable);
        }
        return timetableList;
    }

    /**
     * @目的 开始给进化完的基因编码分配教室
     * @方式 在原来的编码中加上教室编号
     */
    private List<String> assignRoom(Map<String, List<String>> individualMap) {
        List<String> resultList = new ArrayList<>();
        List<String> resultGeneList = geneticAlgorithm.collectGene(individualMap);
        String RoomNo = "";
        for (String gene : resultGeneList){
            LambdaQueryWrapper<Room> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(Room::getAreaNo,ClassUtil.cutGene(ConstantInfo.AREA_NO,gene));
            wrapper.eq(Room::getAttr,ClassUtil.cutGene(ConstantInfo.COURSE_ATTR,gene));
            List<Room> roomList = roomMapper.selectList(wrapper);
            /// roomList - 在限制了areaNo和attr后的roomList
            RoomNo = issueRoom(gene, roomList, resultList);
            gene = gene + RoomNo;
            resultList.add(gene);
        }
        return resultList;
    }

    /**
     * 编码规则为：
     * +班级编号(8)
     * +教师编号(8)
     * +课程编号(8)
     * +课程属性(2)
     * +startWeek(2)
     * +endWeek(2)
     * +biweekly(1)
     * +areaNo(2)
     * +classCount(1)
     * +isFix(1)
     * +上课时间(2)     isFix=0时随机分配上课时间
     * [+教室编号(8)]
     *
     * 最新规则可见ConstantInfo.RULES
     *
     * 进行编码拼接
     */
    public List<String> coding(List<Task> taskList){
        List<String> resultGeneList = new ArrayList<>();
        String isFix = "0";//表示需要修复 之后有空在搞自定义固定上课时间
        for (Task task:taskList){
            //1.不固定上课时间，默认不在填充00
            // 得到每周上课的节数，因为设定2学时未一节课
            int size = task.getWeeksNumber() / 2;
            for (int i = 0;i < size;i++){
                String gene = task.getClassNo()
                        + task.getTeacherNo()
                        + task.getCourseNo()
                        + task.getCourseAttr()
                        + task.getStartWeek()
                        + task.getEndWeek()
                        + task.getBiweekly()
                        + task.getAreaNo()
                        + task.getClassNo()
                        + isFix
                        + ClassUtil.randomTime();
                resultGeneList.add(gene);
            }
        }
        return resultGeneList;
    }

    /**
     * 检查taskList中，是否存在班级的学时总数超过ClassUtil.MAX_TIMESLOT
     * 2，是则抛出异常
     */
    private void checkWeeksNumber(List<Task> taskList){
        //参数List<Task> taskList
        taskList.stream().collect(Collectors.groupingBy(Task::getCourseNo)).forEach((k,v)->{
            int sum = v.stream().mapToInt(Task::getWeeksNumber).sum();
            if (sum > (ClassUtil.MAX_WEEK_TIMESLOT+1) * 2){
                System.out.println("班级："+k+ "的学时超过 "+ClassUtil.MAX_WEEK_TIMESLOT * 2+"，不能排课！");
            }
        });
    }


    /********************************************************************************************************************/

    /**
     * 给不同的基因编码分配教室
     * @param gene          需要分配教室的基因编码
     * @param roomList      教室(限制了areaNo和attr)
     * @param resultList    已分配了教室的编码
     */
    private String issueRoom(String gene, List<Room> roomList, List<String> resultList) {
        String classNo = ClassUtil.cutGene(ConstantInfo.CLASS_NO,gene);
        int studentSize = classesMapper.selectStuSize(classNo);
        return chooseClassroom(studentSize,gene,roomList,resultList);
    }

    /**
     * 给不同课程的基因编码随机选择一个教室
     *
     * @param studentNum    开课的班级的学生人数
     * @param gene          需要安排教室的基因编码
     * @param roomList 教室
     * @description 这是一个递归方法
     */
    private String chooseClassroom(int studentNum, String gene, List<Room> roomList, List<String> resultList) {
        int min = 0;
        int max = roomList.size() - 1;
        int temp = min + (int) (Math.random() * (max + 1 - min));
        Room classRoom = roomList.get(temp);
        //分配教室
        boolean isRoomSuitable = judgeClassroom(studentNum,gene,classRoom,resultList);
        if (isRoomSuitable){
            //该教室满足条件
            return classRoom.getRoomNo();
        }else{
            //不满足，继续找教室
            return chooseClassroom(studentNum,gene,roomList,resultList);
        }
    }

    /**
     * 判断教室是否符合上课班级所需
     * 即：不同属性的课要放在对应属性的教室上课
     * @param room  随机分配教室
     */
    private boolean judgeClassroom(int studentNum, String gene, Room room, List<String> resultList) {
        if (room.getCapacity() >= studentNum){
            //判断该教室上课时间是否重复
            return isFree(gene,resultList,room);
        }
        return false;
    }

    /**
     * 判断同一时间同一个教室是否有多个班级使用
     */
    private boolean isFree(String gene, List<String> resultList, Room room) {
        if (resultList.isEmpty()){
            return true;
        }
        String roomNo1 = room.getRoomNo();
        String timeslot1 = ClassUtil.cutGene(ConstantInfo.TIMESLOT, gene);
        int startWeek1 = Integer.parseInt(ClassUtil.cutGene(ConstantInfo.START_WEEK,gene));
        int endWeek1 = Integer.parseInt(ClassUtil.cutGene(ConstantInfo.END_WEEK,gene));
        int biweekly1 = Integer.parseInt(ClassUtil.cutGene(ConstantInfo.BIWEEKLY,gene));

        for (String resultGene:resultList){
            String roomNo2 = ClassUtil.cutGene(ConstantInfo.ROOM_NO, resultGene);
            String timeslot2 = ClassUtil.cutGene(ConstantInfo.TIMESLOT, resultGene);
            int startWeek2 = Integer.parseInt(ClassUtil.cutGene(ConstantInfo.START_WEEK,resultGene));
            int endWeek2 = Integer.parseInt(ClassUtil.cutGene(ConstantInfo.END_WEEK,resultGene));
            int biweekly2 = Integer.parseInt(ClassUtil.cutGene(ConstantInfo.BIWEEKLY,resultGene));

            boolean intervalOverlap = startWeek1 <= endWeek2 && startWeek2 <= endWeek1;
            boolean timeOverlap = timeslot1.equals(timeslot2) && intervalOverlap && biweekly1+biweekly2 != 3;
            if (timeOverlap && roomNo1.equals(roomNo2)) {
                return false;
            }
        }
        return true;
    }

}
